package proxy;

import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.LinkedHashMap;
import java.util.Map;
import com.Communicator;
import core.*;
import interfaces.*;

/**
 * Classe responsavel por processar as mensagens recebidas pelo channel do Manager
 * 
 * @author robertow
 * 
 */
public class ManagerProxy extends Thread implements IManagerProxy, IProxy {
	
	// Proxurador do gerente
	static ManagerProxy managerProxy;

	// Cria um atualizador de estado das filas de jobs
	//private CheckStatusLists checkStateLists;
	
	// Cria um gerente de tarefas
	private JobManager jobManager;
	
	// Cria um gerente de respostas de tarefas
	private AnswerManager answerManager;
	
	// Cria um gerente de estado de trabalhadores
	private StatusWorkerManager statusWorkerManager;
	
	// Atualizador de Status
    private UpdateStatusManager updateStatusManager;

	// Canal de comunicação do procurador do gerente
	private ICommunicator managerProxyChannel;
	
	// Canal de comunicação com o gerente deste gerente
	private ICommunicator bossChannelSend;
	
	// Canal de comunicação com o trabalhador deste gerente
	private ICommunicator workerChannelSend;
	
	// Descrição do canal de comunicação do procurador do gerente;
	private String managerProxyChannelDesc;
	
	// Lista para armazenar todas as conexões sockets feitas por este gerente
	private Map<String, SocketChannel> connectionsList;
		
	// Identificação do procurador do gerente
	private String managerProxyId;
	
	private String defaultIP;
	
	// Descricao do resultado de um calcula em um array de bytes
	private byte[] resultDesc;
	
	// Descricao do job em um array de bytes
	private  byte[] jobDesc;
	
	// Tipo da mensagem recebida
	private short msg_type;
	
	// Tamanho da mensagem recebida
	private  int size;
	private int descSize;
	
	// Quantidade de trabalho
	private  int workload;
	
	// Capacidade de trabalho
	private float capacity;
	
	// Data de atualizacao de um determinado status
	private long updateDate;
	
	// Identificacao do trabalhador
	private String workerId;
	
	// Identificacao da tarefa
	private String jobId;
	
	// Descricao do canal de comunicacao
	private String channelDesc;
	
	private boolean running = true;
	
	public static void main(String[] args) {
		String id = args[0];
		String defaultIP = args[1]; 
		String launcherDescription = args[2];

		if (args.length != 3) {
			System.out.println("ManagerProxy => ");
			System.out.println("\t Faltam argumentos (ID_NOH ChannelChefe \n");
			System.exit(1);
		}

		managerProxy = new ManagerProxy(id, defaultIP);

		try {
			ICommunicator launcherChannelSend = new Communicator();
			launcherChannelSend.connectServer(launcherDescription);
			launcherChannelSend.sendMsgNodeStarted(launcherChannelSend.getSocket(), id, managerProxy.getChannelDesc());

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public ManagerProxy(String managerProxyId, String defaultIP) {
		try {
			this.managerProxyId = managerProxyId;
			
			this.defaultIP = defaultIP;
			
			// Instancia um novo canal de comunicacao
			this.managerProxyChannel = new Communicator(this.defaultIP);
			
			// Obtem a descricao do canal de comunicacao
			this.managerProxyChannelDesc = this.managerProxyChannel.serverChannelDescription();

			// Instancia um novo gerente de tarefas
			this.jobManager = new JobManager();
			this.jobManager.setID(this.managerProxyId);
			this.jobManager.setManagerProxy(this);
			
			// Instancia um novo gerente de trabalhadores
			this.statusWorkerManager = new StatusWorkerManager();
			this.statusWorkerManager.setID(managerProxyId);
			this.statusWorkerManager.setManagerProxy(this);
			
			// Instancia um novo gerente de tarefas
			this.answerManager = new AnswerManager();
			this.answerManager.setID(managerProxyId);
			this.answerManager.setManagerProxy(this);
			
			this.jobManager.setStatusMManager(this.statusWorkerManager);
			this.jobManager.setAnswerManager(this.answerManager);
			this.jobManager.start();
			
			this.statusWorkerManager.setJobManager(this.jobManager);
			this.statusWorkerManager.setAnswerMManager(this.answerManager);
			this.statusWorkerManager.start();
			
			this.answerManager.setJobManager(this.jobManager);
			this.answerManager.setStatusMManager(this.statusWorkerManager);
			this.answerManager.start();
			
			// Instancia o objeto e inicia a thread responsavel por atualizar o status deste gerente
			this.updateStatusManager = new UpdateStatusManager(this, this.statusWorkerManager);
								
			// Instancia uma nova lista sincronizada para armazenar as conexoes sockets
			this.connectionsList = new LinkedHashMap<String, SocketChannel>();
			
			//this.checkStateLists = new CheckStatusLists(this.jobManager);
			//this.checkStateLists.start();
			
			System.out.println("ManagerProxy iniciada =>");
			System.out.println("\t Seu id eh : " + this.managerProxyId);
			System.out.println("\t Seu canal eh: " + this.getChannelDesc() + "\n");

			this.start();	
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public void run() {
		try {
			ByteBuffer buf = null;
			
			/////////////////////////////////////////////////////////////////////////
			System.out.println("ManagerProxy =>");
			System.out.println("\t Recebendo Mensagens ... \n");
			/////////////////////////////////////////////////////////////////////////
			while (this.running) {
				buf = managerProxyChannel.receiveMessages();
				msg_type = buf.getShort();
				size = buf.getInt();
				switch (msg_type) {
					case Config.NEW_WORKER:
						workerId = Communicator.readString(buf).trim();
						channelDesc = Communicator.readString(buf).trim();
						this.statusWorkerManager.newWorker(workerId, channelDesc);
					break;
					
					case Config.JOB_RESULT:
						workerId = Communicator.readString(buf).trim();
						jobId = Communicator.readString(buf).trim();
						descSize = size - 2 - 4 - (workerId.length() + 1) - (jobId.length() + 1);
						resultDesc = new byte[descSize];
						buf.get(resultDesc);
						this.answerManager.jobResult(workerId, jobId, resultDesc);
					break;
					
					case Config.NODE_STATUS:
						workerId = Communicator.readString(buf).trim();
						capacity = buf.getFloat();
						workload = buf.getInt();
						updateDate = buf.getLong();
						this.statusWorkerManager.nodeStatus(workerId, capacity, workload, updateDate);
					break;
					
					case Config.NEW_JOB:
						jobId = Communicator.readString(buf).trim();
						descSize = size - 2 - 4 - (jobId.length() + 1);
						jobDesc = new byte[descSize];
						buf.get(jobDesc);
						this.jobManager.newJob(jobId, jobDesc);
					break;
					
					case Config.BOSS_CONTACT:
						workerId = Communicator.readString(buf).trim();
						
						/////////////////////////////////////////////////////////////////////////
						//System.out.println("ManagetProxy: " + this.managerId + " =>");
						//System.out.println("\t Recebi Msg. BOSS_CONTACT");
						//System.out.println("\t\t O Canal com meu chefe eh: " + workerId +"\n");
						/////////////////////////////////////////////////////////////////////////

						this.managerConnect(workerId);
						this.updateStatusManager.start();
					break;
					
				default:
					System.out.println("ManagerProxy =>");
					System.out.println("\t\t LOST MESSAGE! Type: " + msg_type + "\n");
					break;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void managerConnect(String channelDesc) {
		// TODO Auto-generated method stub
		try {
			this.bossChannelSend = new Communicator();
			this.bossChannelSend.connectServer(channelDesc);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void workerConnect(String workerId, String channelDesc) {
		// TODO Auto-generated method stub
		try {
			this.workerChannelSend = new Communicator ();
			this.workerChannelSend.connectServer(channelDesc);
			this.connectionsList.put(workerId,this.workerChannelSend.getSocket());
		} catch (Exception e ){
			e.printStackTrace();
		}
	}

	@Override
	public synchronized void statusSend(String workerId, float capacity, int workload) {
		// TODO Auto-generated method stub
		try {
			this.bossChannelSend.sendMsgNodeStatus(this.bossChannelSend.getSocket(), workerId, capacity, workload, System.currentTimeMillis());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public synchronized void resultSend(String workerId, String jobId, byte[] jobResultDesc) {
		// TODO Auto-generated method stub
		try {
			this.bossChannelSend.sendMsgJobResult(this.bossChannelSend.getSocket(), workerId, jobId, jobResultDesc);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public synchronized void bossContactSend() {
		// TODO Auto-generated method stub
		try {
			this.workerChannelSend.sendMsgBossContact(this.workerChannelSend.getSocket(), this.getChannelDesc());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public synchronized void newJobSend(IJob job, String workerId) {
		// TODO Auto-generated method stub
		try {
			this.workerChannelSend.sendMsgNewJob(this.getWorkerSocket(workerId), job.getID(), job.getDescription());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void setChannelDesc(String channelDesc) {
		// TODO Auto-generated method stub
		this.managerProxyChannelDesc = channelDesc;
	}

	@Override
	public String getChannelDesc() {
		// TODO Auto-generated method stub
		return this.managerProxyChannelDesc;
	}

	@Override
	public void setChannel(ICommunicator channel) {
		// TODO Auto-generated method stub
		this.managerProxyChannel = channel;
	}

	@Override
	public ICommunicator getChannel() {
		// TODO Auto-generated method stub
		return this.managerProxyChannel;
	}

	@Override
	public synchronized SocketChannel getWorkerSocket(String workerId) {
		// TODO Auto-generated method stub
		try {
			return this.connectionsList.get(workerId);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}